module AC2P
{
	uses interface Boot;
	uses interface Timer<TMilli> as TempTimer;
	uses interface Leds;
	uses interface AMSend as RadioSend;
	uses interface AMSend as SerialSend;
	uses interface Receive as RadioReceive;
	uses interface Receive as SerialReceive;
	uses interface Packet as RadioPacket;
	uses interface Packet as SerialPacket;
	uses interface SplitControl as RadioControl;
	uses interface SplitControl as SerialControl;
	uses interface Read<uint16_t> as TempRead;

	uses interface HplMsp430GeneralIO as gpio61; //add these three lines to edited chris' code
	uses interface HplMsp430GeneralIO as gpio62; 
	uses interface Timer<TMilli> as PWMTimer;
}
implementation
{
	uint16_t storeReading;
	message_t buffer;
	uint8_t epsilon = 0x20;
	uint16_t targetTemp =0x1A80;
	uint8_t ledSetting = 0;
	uint8_t counter = 0;

	bool isTemperatureNode = FALSE;
	bool isVentControllerNode = FALSE;
	bool isHubNode = FALSE;
    
	uint16_t PWMWidth = 0x1;  //pulse width in ms - must be divisible by PWM_PERIODIC
	uint16_t PWMCount = 0x0;  //counter in ms like PWM_PERIODIC
	uint16_t PWMPeriod = 0x19; //period of PWM signal in ms.n
	uint8_t ventPosition;
	uint8_t systemMode = 0x1;

	// The temperature periodic - 15 seconds
	const uint16_t TEMPERATURE_PERIODIC = 0x400 * 5;
	const uint16_t PWM_PERIODIC = 0x1;  //1000 us
	const uint8_t VENT_OPEN = 0x00;
	const uint8_t VENT_CLOSE =0x01;
	const uint8_t MODE_HEAT = 0x00;
	const uint8_t MODE_COOL = 0X01;

	ac2_msg_buffer_t lastRadioMessage;
	ac2_msg_buffer_t lastSerialMessage;

	task void handleTempTimer();
	task void handleTemperatureReading();
	task void parsePacket();
	task void sendSerialPacket();
	task void sendRadioPacket();
	task void generatePWMSignal();
	task void setSystemIO();
	task void sendVentStatus();

	event void Boot.booted()
	{
		if (TOS_NODE_ID == HUB_NODE)
			isHubNode = TRUE; 
		else if (TOS_NODE_ID >= FIRST_TEMP_NODE && (TOS_NODE_ID & 0x0F) == 0x00)
			isTemperatureNode = TRUE;
		else if (TOS_NODE_ID >= FIRST_TEMP_NODE && (TOS_NODE_ID & 0x0F) != 0x00)
			isVentControllerNode = TRUE;

		if (call RadioControl.start() != SUCCESS)
			call Leds.set(7);

		if (isHubNode)
			if (call SerialControl.start() != SUCCESS)
				call Leds.set(6);
                
		if (isVentControllerNode) {
			ledSetting |= 0x04;
			ventPosition = VENT_OPEN;
			post setSystemIO();
		}
	}
	
    	// Occurs when the radio control start has completed
	event void RadioControl.startDone(error_t err)
	{
		if(err != SUCCESS)
			call Leds.set(7);
		else
		{
			if (isTemperatureNode)
				call TempTimer.startPeriodic(TEMPERATURE_PERIODIC);		
			if (isVentControllerNode)
				call PWMTimer.startPeriodic(PWM_PERIODIC);
		}
	}
	
	// Nothing done here
	event void RadioControl.stopDone(error_t err) { }

	// Occurs when the serial control start has completed
	event void SerialControl.startDone(error_t err)
	{
		if (err != SUCCESS)
		{
			call Leds.set(7);
		}
	}

	// Nothing done here
	event void SerialControl.stopDone(error_t err) { }

	// Display all LEDs on error
	event void SerialSend.sendDone(message_t* msg, error_t err)
	{
		if (err != SUCCESS) call Leds.set(7);
	}
	
	event void TempTimer.fired()
	{
		post handleTempTimer();
	}
	
	event void PWMTimer.fired()
	{
		post generatePWMSignal();
	}

	// Take a reading from the thermometer
	task void handleTempTimer()
	{
		// If fails, try again
		if(call TempRead.read() != SUCCESS)
			post handleTempTimer();
	}

	// Occurs when the temperature reading is done
	event void TempRead.readDone(error_t err, uint16_t val)
	{
		if(err != SUCCESS)
			post handleTempTimer();
		else
		{
			ledSetting ^= 0x02;
			call Leds.set(ledSetting);
			storeReading = val;
			post handleTemperatureReading();
		}
	}
	
	// Write the reading to the network
	task void handleTemperatureReading()
	{
		temp_update_t * payload = (temp_update_t *)call RadioPacket.getPayload(&buffer, sizeof(temp_update_t));
		payload->header.source = TOS_NODE_ID;
		payload->header.destination = HUB_NODE;
		payload->header.function = FUNCTION_TEMPERATURE_UPDATE;
		payload->reading = storeReading;
		payload->counter = counter++;

		if(call RadioSend.send(AM_BROADCAST_ADDR, &buffer, sizeof(temp_update_t)) != SUCCESS) {
			post handleTemperatureReading();
		}
	}

	task void sendVentStatus()
	{
		if (isVentControllerNode)
		{
			state_t* payload = (state_t*)call RadioPacket.getPayload(&buffer, sizeof(state_t));
			payload->header.source = TOS_NODE_ID;
			payload->header.destination = HUB_NODE;
			payload->header.function = FUNCTION_VENT_STATUS_UPDATE;
			payload->ventState = ventPosition;
			payload->mode = systemMode;
			payload->unused1 = targetTemp >> 8;

			if (call RadioSend.send(AM_BROADCAST_ADDR, &buffer, sizeof(state_t)) != SUCCESS)
			{
				post sendVentStatus();
			}
		}
	}
	
	event void RadioSend.sendDone(message_t * msg, error_t err)
	{
		if(err != SUCCESS)
			post handleTemperatureReading();
	}

	event message_t* SerialReceive.receive(message_t* msg, void* payload, uint8_t len)
	{
		ac2_msg_buffer_t* ac2_msg = (ac2_msg_buffer_t*)payload;
		if (isHubNode)
		{
			// Copy the message
			lastSerialMessage = *ac2_msg;
			call Leds.set(call Leds.get() ^ 4);
			post sendRadioPacket();
		}
		return msg;
	}
	
	event message_t * RadioReceive.receive(message_t * msg, void * payload, uint8_t len)
	{
		ac2_header_t * ac2_payload = (ac2_header_t *)payload;

		// Essentialy, if this is the hub node, we just want to relay every message we receive
		if (isHubNode)
		{
			// Copy the message
			ac2_msg_buffer_t* pBuffer = (ac2_msg_buffer_t*)payload;
			lastRadioMessage = *pBuffer;

			ledSetting ^= 0x02;
			call Leds.set(ledSetting);

			call Leds.set(0x02);
				post sendSerialPacket(); 
		}
		else if ((TOS_NODE_ID & 0xF0) == (ac2_payload->destination & 0xF0) || (ac2_payload->source == (TOS_NODE_ID & 0xF0)))
		{
			switch (ac2_payload->function)
			{
				case FUNCTION_TEMPERATURE_UPDATE:
					atomic {
						temp_update_t* pTempUpdate = (temp_update_t*)ac2_payload;
						storeReading = pTempUpdate->reading;
					}
					post parsePacket();
					break;
				case FUNCTION_SET_TARGET_TEMPERATURE:
					atomic {
						set_target_temp_t* pTempUpdate = (set_target_temp_t*)ac2_payload;
						targetTemp = pTempUpdate->rawTemp;
					}
					post parsePacket();
					break;
				case FUNCTION_SET_MODE:
				//case FUNCTION_MODE_UPDATE:
					atomic {
						state_t* pModeUpdate = (state_t*)ac2_payload;
						systemMode = pModeUpdate->mode;
					}
					post parsePacket();
					break;
				case FUNCTION_REQUEST_VENT_STATUS:
					// Verify that this request was for this node...compare high nibble
					// post sendVentStatus();
					break;
				default:
					break;
			}
			post sendVentStatus();
		}  
		return msg;
	}

	task void sendSerialPacket()
	{
		ac2_msg_buffer_t* pMesg = (ac2_msg_buffer_t*)call SerialPacket.getPayload(&buffer, sizeof(ac2_msg_buffer_t));

		call Leds.set(0x1);
		*pMesg = lastRadioMessage;

		if (call SerialSend.send(AM_SERIAL_MSG, &buffer, sizeof(ac2_msg_buffer_t)) != SUCCESS)
		{
			ledSetting ^= 0x01;
			call Leds.set(ledSetting);
			post sendSerialPacket();
		}
	}

	task void sendRadioPacket()
	{
		ac2_msg_buffer_t* pMesg = (ac2_msg_buffer_t*)call RadioPacket.getPayload(&buffer, sizeof(ac2_msg_buffer_t));
		*pMesg = lastSerialMessage;

		call Leds.set(call Leds.get() ^ 2);

		if (call RadioSend.send(AM_BROADCAST_ADDR, &buffer, sizeof(ac2_msg_buffer_t)) != SUCCESS)
		{
			call Leds.set(0x5);
			post sendRadioPacket();
		}
	}
	
	task void parsePacket() 
	{
		uint8_t newVentPosition;		
	
		if (isVentControllerNode) {
			atomic {
				newVentPosition = ((storeReading > (targetTemp+epsilon)) & (systemMode == MODE_HEAT))
				                | ((storeReading < (targetTemp-epsilon)) & (systemMode == MODE_COOL)); 
			}
			atomic {
				if (ventPosition != newVentPosition)
				{
					ledSetting &= 0xFC;
					ledSetting |= (newVentPosition + 1);	
					ventPosition = newVentPosition;
					//post sendVentStatus();
				}
				else ledSetting ^= (newVentPosition + 1);
			}

			call Leds.set(ledSetting);
		}
	}
	
	task void setSystemIO() 
	{
		call gpio61.makeOutput();
		call gpio61.selectIOFunc();
		call gpio62.makeOutput();
		call gpio62.selectIOFunc();
		call gpio62.set();
	}

	task void generatePWMSignal() 
	{
		if (ventPosition == VENT_OPEN)
			PWMWidth = 2; //ms
		if (ventPosition == VENT_CLOSE)
			PWMWidth = 1; //ms

		if (PWMCount <= PWMWidth)
			call gpio61.set();
		else 
			call gpio61.clr();
		if (PWMCount > PWMPeriod)
		{
			PWMCount = 0;
		}
		PWMCount += PWM_PERIODIC;
	}

}
